﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Moq;
using NHibernate;
using NHibernate.Identity;
using NHibernate.Identity.Internal;
using NUnit.Framework;

namespace NHibernateIdentity.Tests
{
    [TestFixture]
    public class UserStoreTests
    {

        private Mock<IEntityStore<IdentityUser>> _mockUserStore;
        private Mock<IEntityStore<IdentityRole>> _mockRoleStore;
        private Mock<IEntityStore<IdentityUserLogin>> _mockLogins;
        private Mock<IEntityStore<IdentityUserClaim>> _mockUserClaims;
        private Mock<ISession> _sessionMock;
        private UserStore<IdentityUser> _userStore;

        private List<IdentityUser> _users;
        private List<IdentityRole> _roles;

        [SetUp]
        public void Setup()
        {
            // Create instance
            _mockUserStore = new Mock<IEntityStore<IdentityUser>>();
            _mockRoleStore = new Mock<IEntityStore<IdentityRole>>();
            _mockLogins = new Mock<IEntityStore<IdentityUserLogin>>();
            _mockUserClaims = new Mock<IEntityStore<IdentityUserClaim>>();
            _sessionMock = new Mock<ISession>();

            _users = new List<IdentityUser>
            {
                new IdentityUser("Test1"),
                new IdentityUser("Test2"),
                new IdentityUser("Test3"),
                new IdentityUser("Test4")
            };

            _roles = new List<IdentityRole>
            {
                new IdentityRole("Role1"),
                new IdentityRole("Role2"),
                new IdentityRole("Role3"),
                new IdentityRole("Role4")
            };

            // Setup
            _mockUserStore.Setup(s => s.Records())
                .Returns(Task.FromResult(_users.AsQueryable()));
            _mockRoleStore.Setup(s => s.Records())
                .Returns(Task.FromResult(_roles.AsQueryable()));

            // Create instance
            _userStore = new UserStore<IdentityUser>(_mockUserStore.Object, _mockRoleStore.Object, _mockLogins.Object,
                _mockUserClaims.Object);
        }

        [Test]
        public void SessionCtor()
        {
            // Setup
            _userStore = new UserStore<IdentityUser>(_sessionMock.Object);

            // Assert
            Assert.IsNotNull(_userStore.Session);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SessionCtorException()
        {
            // Perform
            _userStore = new UserStore<IdentityUser>(null);
        }

        [Test]
        public async void CanCreateUser()
        {
            // Perform
            var user = new IdentityUser("Test5");
            await _userStore.CreateAsync(user);

            // Assert
            _mockUserStore.Verify(v => v.Save(user), Times.Once);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanCreateUserException()
        {
            await _userStore.CreateAsync(null);
        }

        [Test]
        public async void CanUpdateUser()
        {
            // Perform
            _users[0].UserName = "Test6";
            await _userStore.UpdateAsync(_users[0]);

            // Assert
            _mockUserStore.Verify(v => v.Save(_users[0]), Times.Once);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanUpdateUserException()
        {
            await _userStore.UpdateAsync(null);
        }

        [Test]
        public async void CanDeleteUser()
        {
            // Perform
            await _userStore.DeleteAsync(_users[0]);

            // Assert
            _mockUserStore.Verify(v => v.Delete(_users[0]), Times.Once);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanDeleteUserException()
        {
            await _userStore.DeleteAsync(null);
        }

        [Test]
        public async void CanFindUserByLogin()
        {
            // Setup
            _mockLogins.Setup(s => s.Records())
                .Returns(Task.FromResult(new List<IdentityUserLogin>
                {
                    new IdentityUserLogin {LoginProvider = "google.com", ProviderKey = "key=123456", User = _users[2]}
                }.AsQueryable()));
            _userStore = new UserStore<IdentityUser>(_mockUserStore.Object, _mockRoleStore.Object, _mockLogins.Object,
                _mockUserClaims.Object);

            // Perform
            var user = await _userStore.FindAsync(new UserLoginInfo("google.com", "key=123456"));

            // Assert
            Assert.AreEqual(user.UserName, _users[2].UserName);
        }

        [Test]
        public async void CanFindUserByLoginNotExist()
        {
            // Setup
            _mockLogins.Setup(s => s.Records())
                .Returns(Task.FromResult(new List<IdentityUserLogin>
                {
                    new IdentityUserLogin {LoginProvider = "google.com", ProviderKey = "key=123456", User = _users[2]}
                }.AsQueryable()));
            _userStore = new UserStore<IdentityUser>(_mockUserStore.Object, _mockRoleStore.Object, _mockLogins.Object,
                _mockUserClaims.Object);

            // Perform
            var user = await _userStore.FindAsync(new UserLoginInfo("google2.com", "key=123456"));

            // Assert
            Assert.IsNull(user);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanFindUserByLoginException()
        {
            await _userStore.FindAsync(null);
        }

        [Test]
        public async void CanFindUserById()
        {
            // Perform
            var user = await _userStore.FindByIdAsync(_users[2].Id);

            // Assert
            Assert.AreEqual(user.UserName, _users[2].UserName);
        }

        [Test]
        public async void CanFindUserByName()
        {
            // Perform
            var user = await _userStore.FindByNameAsync(_users[2].UserName);

            // Assert
            Assert.AreEqual(user.Id, _users[2].Id);
        }

        [Test]
        public async void CanAddLogin()
        {
            // Perform
            await _userStore.AddLoginAsync(_users[2], new UserLoginInfo("google.com", "key=123456"));

            // Assert
            Assert.AreEqual(1, _users[2].Logins.Count);
            Assert.AreEqual("google.com", _users[2].Logins[0].LoginProvider);
            Assert.AreEqual("key=123456", _users[2].Logins[0].ProviderKey);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanAddLoginExceptionUser()
        {
            await _userStore.AddLoginAsync(null, new UserLoginInfo("google.com", "key=123456"));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanAddLoginExceptionLogin()
        {
            await _userStore.AddLoginAsync(_users[2], null);
        }

        [Test]
        public async void CanRemoveLogin()
        {
            // Perform
            await _userStore.AddLoginAsync(_users[2], new UserLoginInfo("google.com", "key=123456"));
            await _userStore.RemoveLoginAsync(_users[2], new UserLoginInfo("google.com", "key=123456"));

            // Assert
            Assert.AreEqual(0, _users[2].Logins.Count);
            _mockLogins.Verify(v => v.Delete(It.IsAny<IdentityUserLogin>()), Times.Once);
        }

        [Test]
        public async void CanRemoveLoginNotExist()
        {
            // Perform
            await _userStore.AddLoginAsync(_users[2], new UserLoginInfo("google.com", "key=123456"));
            await _userStore.RemoveLoginAsync(_users[2], new UserLoginInfo("google2.com", "key=123456"));

            // Assert
            Assert.AreEqual(1, _users[2].Logins.Count);
            Assert.AreEqual("google.com", _users[2].Logins[0].LoginProvider);
            Assert.AreEqual("key=123456", _users[2].Logins[0].ProviderKey);
            _mockLogins.Verify(v => v.Delete(It.IsAny<IdentityUserLogin>()), Times.Never);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanRemoveLoginExceptionUser()
        {
            await _userStore.RemoveLoginAsync(null, new UserLoginInfo("google.com", "key=123456"));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanRemoveLoginExceptionLogin()
        {
            await _userStore.RemoveLoginAsync(_users[2], null);
        }

        [Test]
        public async void CanGetLogins()
        {
            // Perform
            await _userStore.AddLoginAsync(_users[2], new UserLoginInfo("google.com", "key=123456"));
            var logins = await _userStore.GetLoginsAsync(_users[2]);

            // Assert
            Assert.AreEqual(1, logins.Count);
            Assert.AreEqual("google.com", logins[0].LoginProvider);
            Assert.AreEqual("key=123456", logins[0].ProviderKey);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanGetLoginsException()
        {
            await _userStore.GetLoginsAsync(null);
        }

        [Test]
        public async void CanAddClaim()
        {
            // Perform
            await _userStore.AddClaimAsync(_users[2], new Claim("claim1", "value1"));

            // Assert
            Assert.AreEqual(1, _users[2].Claims.Count);
            Assert.AreEqual("claim1", _users[2].Claims[0].ClaimType);
            Assert.AreEqual("value1", _users[2].Claims[0].ClaimValue);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanAddClaimExceptionUser()
        {
            await _userStore.AddClaimAsync(null, new Claim("claim1", "value1"));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanAddClaimExceptionClaim()
        {
            await _userStore.AddClaimAsync(_users[2], null);
        }

        [Test]
        public async void CanRemoveClaim()
        {
            // Perform
            await _userStore.AddClaimAsync(_users[2], new Claim("claim1", "value1"));
            await _userStore.RemoveClaimAsync(_users[2], new Claim("claim1", "value1"));

            // Assert
            Assert.AreEqual(0, _users[2].Claims.Count);
            _mockUserClaims.Verify(v => v.Delete(It.IsAny<IdentityUserClaim>()), Times.Once);
        }

        [Test]
        public async void CanRemoveClaimNotExist()
        {
            // Perform
            await _userStore.AddClaimAsync(_users[2], new Claim("claim1", "value1"));
            await _userStore.RemoveClaimAsync(_users[2], new Claim("claim2", "value2"));

            // Assert
            Assert.AreEqual(1, _users[2].Claims.Count);
            Assert.AreEqual("claim1", _users[2].Claims[0].ClaimType);
            Assert.AreEqual("value1", _users[2].Claims[0].ClaimValue);
            _mockUserClaims.Verify(v => v.Delete(It.IsAny<IdentityUserClaim>()), Times.Never);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanRemoveClaimExceptionUser()
        {
            await _userStore.RemoveClaimAsync(null, new Claim("claim1", "value1"));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanRemoveClaimExceptionClaim()
        {
            await _userStore.RemoveClaimAsync(_users[2], null);
        }

        [Test]
        public async void CanGetClaims()
        {
            // Perform
            await _userStore.AddClaimAsync(_users[2], new Claim("claim1", "value1"));
            var claims = await _userStore.GetClaimsAsync(_users[2]);

            // Assert
            Assert.AreEqual(1, claims.Count);
            Assert.AreEqual("claim1", claims[0].Type);
            Assert.AreEqual("value1", claims[0].Value);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanGetClaimsException()
        {
            await _userStore.GetClaimsAsync(null);
        }

        [Test]
        public async void CanAddToRole()
        {
            // Perform
            await _userStore.AddToRoleAsync(_users[2], "Role2");

            // Assert
            Assert.AreEqual(1, _users[2].Roles.Count);
            Assert.AreEqual("Role2", _users[2].Roles[0].Name);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanAddToRoleExceptionUser()
        {
            await _userStore.AddToRoleAsync(null, "Role2");
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public async void CanAddToRoleExceptionRole()
        {
            await _userStore.AddToRoleAsync(_users[2], null);
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public async void CanAddToRoleNotExist()
        {
            await _userStore.AddToRoleAsync(_users[2], "Role5");
        }

        [Test]
        public async void CanRemoveFromRole()
        {
            // Perform
            await _userStore.AddToRoleAsync(_users[2], "Role2");
            await _userStore.RemoveFromRoleAsync(_users[2], "Role2");

            // Assert
            Assert.AreEqual(0, _users[2].Roles.Count);
            _mockUserStore.Verify(v => v.Save(_users[2]), Times.Once);
        }

        [Test]
        public async void CanRemoveFromRoleNotExist()
        {
            // Perform
            await _userStore.AddToRoleAsync(_users[2], "Role2"); 
            await _userStore.RemoveFromRoleAsync(_users[2], "Role5");

            // Assert
            Assert.AreEqual(1, _users[2].Roles.Count);
            Assert.AreEqual("Role2", _users[2].Roles[0].Name);
            _mockUserStore.Verify(v => v.Save(_users[2]), Times.Never);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanRemoveFromRoleExceptionUser()
        {
            await _userStore.RemoveFromRoleAsync(null, "Role2");
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public async void CanRemoveFromRoleExceptionRole()
        {
            await _userStore.RemoveFromRoleAsync(_users[2], null);
        }

        [Test]
        public async void CanGetRoles()
        {
            // Perform
            await _userStore.AddToRoleAsync(_users[2], "Role2"); 
            var roles = await _userStore.GetRolesAsync(_users[2]);

            // Assert
            Assert.AreEqual(1, roles.Count);
            Assert.AreEqual("Role2", roles[0]);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanGetRolesException()
        {
            await _userStore.GetRolesAsync(null);
        }

        [Test]
        public async void CanCheckIfInRoleTrue()
        {
            // Perform
            await _userStore.AddToRoleAsync(_users[2], "Role2");
            var result = await _userStore.IsInRoleAsync(_users[2], "Role2");

            // Assert
            Assert.IsTrue(result);
        }

        [Test]
        public async void CanCheckIfInRoleFalse()
        {
            // Perform
            await _userStore.AddToRoleAsync(_users[2], "Role2");
            var result = await _userStore.IsInRoleAsync(_users[2], "Role3");

            // Assert
            Assert.IsFalse(result);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public async void CanCheckIfInRoleExceptionUser()
        {
            await _userStore.IsInRoleAsync(null, "Role2");
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public async void CanCheckIfInRoleExceptionRole()
        {
            await _userStore.IsInRoleAsync(_users[2], null);
        }

        [Test]
        [ExpectedException(typeof(ObjectDisposedException))]
        public async void ThrowDisposed()
        {
            // Perform
            _userStore.Dispose();
            await _userStore.FindByNameAsync(_users[2].UserName);
        }

        [Test]
        [ExpectedException(typeof(ObjectDisposedException))]
        public async void ThrowDisposedWithSession()
        {
            // Setup

            _userStore = new UserStore<IdentityUser>(_sessionMock.Object);

            // Assert
            Assert.IsNotNull(_userStore.Session);

            // Perform
            _userStore.Dispose();
            await _userStore.FindByNameAsync(_roles[2].Name);
        }
    }
}